1   /*
2    * Copyright (C) 2011 The Guava Authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.google.common.util.concurrent;
18  
19  import static com.google.common.base.Preconditions.checkNotNull;
20  
21  import com.google.common.annotations.GwtCompatible;
22  import com.google.common.base.Function;
23  import com.google.common.collect.Maps;
24  
25  import java.util.Collections;
26  import java.util.Map;
27  import java.util.concurrent.ConcurrentHashMap;
28  import java.util.concurrent.atomic.AtomicLong;
29  
30  /**
31   * A map containing {@code long} values that can be atomically updated. While writes to a
32   * traditional {@code Map} rely on {@code put(K, V)}, the typical mechanism for writing to this map
33   * is {@code addAndGet(K, long)}, which adds a {@code long} to the value currently associated with
34   * {@code K}. If a key has not yet been associated with a value, its implicit value is zero.
35   *
36   * <p>Most methods in this class treat absent values and zero values identically, as individually
37   * documented. Exceptions to this are {@link #containsKey}, {@link #size}, {@link #isEmpty},
38   * {@link #asMap}, and {@link #toString}.
39   *
40   * <p>Instances of this class may be used by multiple threads concurrently. All operations are
41   * atomic unless otherwise noted.
42   *
43   * <p><b>Note:</b> If your values are always positive and less than 2^31, you may wish to use a
44   * {@link com.google.common.collect.Multiset} such as
45   * {@link com.google.common.collect.ConcurrentHashMultiset} instead.
46   *
47   * <b>Warning:</b> Unlike {@code Multiset}, entries whose values are zero are not automatically
48   * removed from the map. Instead they must be removed manually with {@link #removeAllZeros}.
49   *
50   * @author Charles Fry
51   * @since 11.0
52   */
53  @GwtCompatible
54  public final class AtomicLongMap<K> {
55    private final ConcurrentHashMap<K, AtomicLong> map;
56  
57    private AtomicLongMap(ConcurrentHashMap<K, AtomicLong> map) {
58      this.map = checkNotNull(map);
59    }
60  
61    /**
62     * Creates an {@code AtomicLongMap}.
63     */
64    public static <K> AtomicLongMap<K> create() {
65      return new AtomicLongMap<K>(new ConcurrentHashMap<K, AtomicLong>());
66    }
67  
68    /**
69     * Creates an {@code AtomicLongMap} with the same mappings as the specified {@code Map}.
70     */
71    public static <K> AtomicLongMap<K> create(Map<? extends K, ? extends Long> m) {
72      AtomicLongMap<K> result = create();
73      result.putAll(m);
74      return result;
75    }
76  
77    /**
78     * Returns the value associated with {@code key}, or zero if there is no value associated with
79     * {@code key}.
80     */
81    public long get(K key) {
82      AtomicLong atomic = map.get(key);
83      return atomic == null ? 0L : atomic.get();
84    }
85  
86    /**
87     * Increments by one the value currently associated with {@code key}, and returns the new value.
88     */
89    public long incrementAndGet(K key) {
90      return addAndGet(key, 1);
91    }
92  
93    /**
94     * Decrements by one the value currently associated with {@code key}, and returns the new value.
95     */
96    public long decrementAndGet(K key) {
97      return addAndGet(key, -1);
98    }
99  
100   /**
101    * Adds {@code delta} to the value currently associated with {@code key}, and returns the new
102    * value.
103    */
104   public long addAndGet(K key, long delta) {
105     outer: for (;;) {
106       AtomicLong atomic = map.get(key);
107       if (atomic == null) {
108         atomic = map.putIfAbsent(key, new AtomicLong(delta));
109         if (atomic == null) {
110           return delta;
111         }
112         // atomic is now non-null; fall through
113       }
114 
115       for (;;) {
116         long oldValue = atomic.get();
117         if (oldValue == 0L) {
118           // don't compareAndSet a zero
119           if (map.replace(key, atomic, new AtomicLong(delta))) {
120             return delta;
121           }
122           // atomic replaced
123           continue outer;
124         }
125 
126         long newValue = oldValue + delta;
127         if (atomic.compareAndSet(oldValue, newValue)) {
128           return newValue;
129         }
130         // value changed
131       }
132     }
133   }
134 
135   /**
136    * Increments by one the value currently associated with {@code key}, and returns the old value.
137    */
138   public long getAndIncrement(K key) {
139     return getAndAdd(key, 1);
140   }
141 
142   /**
143    * Decrements by one the value currently associated with {@code key}, and returns the old value.
144    */
145   public long getAndDecrement(K key) {
146     return getAndAdd(key, -1);
147   }
148 
149   /**
150    * Adds {@code delta} to the value currently associated with {@code key}, and returns the old
151    * value.
152    */
153   public long getAndAdd(K key, long delta) {
154     outer: for (;;) {
155       AtomicLong atomic = map.get(key);
156       if (atomic == null) {
157         atomic = map.putIfAbsent(key, new AtomicLong(delta));
158         if (atomic == null) {
159           return 0L;
160         }
161         // atomic is now non-null; fall through
162       }
163 
164       for (;;) {
165         long oldValue = atomic.get();
166         if (oldValue == 0L) {
167           // don't compareAndSet a zero
168           if (map.replace(key, atomic, new AtomicLong(delta))) {
169             return 0L;
170           }
171           // atomic replaced
172           continue outer;
173         }
174 
175         long newValue = oldValue + delta;
176         if (atomic.compareAndSet(oldValue, newValue)) {
177           return oldValue;
178         }
179         // value changed
180       }
181     }
182   }
183 
184   /**
185    * Associates {@code newValue} with {@code key} in this map, and returns the value previously
186    * associated with {@code key}, or zero if there was no such value.
187    */
188   public long put(K key, long newValue) {
189     outer: for (;;) {
190       AtomicLong atomic = map.get(key);
191       if (atomic == null) {
192         atomic = map.putIfAbsent(key, new AtomicLong(newValue));
193         if (atomic == null) {
194           return 0L;
195         }
196         // atomic is now non-null; fall through
197       }
198 
199       for (;;) {
200         long oldValue = atomic.get();
201         if (oldValue == 0L) {
202           // don't compareAndSet a zero
203           if (map.replace(key, atomic, new AtomicLong(newValue))) {
204             return 0L;
205           }
206           // atomic replaced
207           continue outer;
208         }
209 
210         if (atomic.compareAndSet(oldValue, newValue)) {
211           return oldValue;
212         }
213         // value changed
214       }
215     }
216   }
217 
218   /**
219    * Copies all of the mappings from the specified map to this map. The effect of this call is
220    * equivalent to that of calling {@code put(k, v)} on this map once for each mapping from key
221    * {@code k} to value {@code v} in the specified map. The behavior of this operation is undefined
222    * if the specified map is modified while the operation is in progress.
223    */
224   public void putAll(Map<? extends K, ? extends Long> m) {
225     for (Map.Entry<? extends K, ? extends Long> entry : m.entrySet()) {
226       put(entry.getKey(), entry.getValue());
227     }
228   }
229 
230   /**
231    * Removes and returns the value associated with {@code key}. If {@code key} is not
232    * in the map, this method has no effect and returns zero.
233    */
234   public long remove(K key) {
235     AtomicLong atomic = map.get(key);
236     if (atomic == null) {
237       return 0L;
238     }
239 
240     for (;;) {
241       long oldValue = atomic.get();
242       if (oldValue == 0L || atomic.compareAndSet(oldValue, 0L)) {
243         // only remove after setting to zero, to avoid concurrent updates
244         map.remove(key, atomic);
245         // succeed even if the remove fails, since the value was already adjusted
246         return oldValue;
247       }
248     }
249   }
250 
251   /**
252    * Removes all mappings from this map whose values are zero.
253    *
254    * <p>This method is not atomic: the map may be visible in intermediate states, where some
255    * of the zero values have been removed and others have not.
256    */
257   public void removeAllZeros() {
258     for (K key : map.keySet()) {
259       AtomicLong atomic = map.get(key);
260       if (atomic != null && atomic.get() == 0L) {
261         map.remove(key, atomic);
262       }
263     }
264   }
265 
266   /**
267    * Returns the sum of all values in this map.
268    *
269    * <p>This method is not atomic: the sum may or may not include other concurrent operations.
270    */
271   public long sum() {
272     long sum = 0L;
273     for (AtomicLong value : map.values()) {
274       sum = sum + value.get();
275     }
276     return sum;
277   }
278 
279   private transient Map<K, Long> asMap;
280 
281   /**
282    * Returns a live, read-only view of the map backing this {@code AtomicLongMap}.
283    */
284   public Map<K, Long> asMap() {
285     Map<K, Long> result = asMap;
286     return (result == null) ? asMap = createAsMap() : result;
287   }
288 
289   private Map<K, Long> createAsMap() {
290     return Collections.unmodifiableMap(
291         Maps.transformValues(map, new Function<AtomicLong, Long>() {
292           @Override
293           public Long apply(AtomicLong atomic) {
294             return atomic.get();
295           }
296         }));
297   }
298 
299   /**
300    * Returns true if this map contains a mapping for the specified key.
301    */
302   public boolean containsKey(Object key) {
303     return map.containsKey(key);
304   }
305 
306   /**
307    * Returns the number of key-value mappings in this map. If the map contains more than
308    * {@code Integer.MAX_VALUE} elements, returns {@code Integer.MAX_VALUE}.
309    */
310   public int size() {
311     return map.size();
312   }
313 
314   /**
315    * Returns {@code true} if this map contains no key-value mappings.
316    */
317   public boolean isEmpty() {
318     return map.isEmpty();
319   }
320 
321   /**
322    * Removes all of the mappings from this map. The map will be empty after this call returns.
323    *
324    * <p>This method is not atomic: the map may not be empty after returning if there were concurrent
325    * writes.
326    */
327   public void clear() {
328     map.clear();
329   }
330 
331   @Override
332   public String toString() {
333     return map.toString();
334   }
335 
336   /*
337    * ConcurrentMap operations which we may eventually add.
338    *
339    * The problem with these is that remove(K, long) has to be done in two phases by definition ---
340    * first decrementing to zero, and then removing. putIfAbsent or replace could observe the
341    * intermediate zero-state. Ways we could deal with this are:
342    *
343    * - Don't define any of the ConcurrentMap operations. This is the current state of affairs.
344    *
345    * - Define putIfAbsent and replace as treating zero and absent identically (as currently
346    *   implemented below). This is a bit surprising with putIfAbsent, which really becomes
347    *   putIfZero.
348    *
349    * - Allow putIfAbsent and replace to distinguish between zero and absent, but don't implement
350    *   remove(K, long). Without any two-phase operations it becomes feasible for all remaining
351    *   operations to distinguish between zero and absent. If we do this, then perhaps we should add
352    *   replace(key, long).
353    *
354    * - Introduce a special-value private static final AtomicLong that would have the meaning of
355    *   removal-in-progress, and rework all operations to properly distinguish between zero and
356    *   absent.
357    */
358 
359   /**
360    * If {@code key} is not already associated with a value or if {@code key} is associated with
361    * zero, associate it with {@code newValue}. Returns the previous value associated with
362    * {@code key}, or zero if there was no mapping for {@code key}.
363    */
364   long putIfAbsent(K key, long newValue) {
365     for (;;) {
366       AtomicLong atomic = map.get(key);
367       if (atomic == null) {
368         atomic = map.putIfAbsent(key, new AtomicLong(newValue));
369         if (atomic == null) {
370           return 0L;
371         }
372         // atomic is now non-null; fall through
373       }
374 
375       long oldValue = atomic.get();
376       if (oldValue == 0L) {
377         // don't compareAndSet a zero
378         if (map.replace(key, atomic, new AtomicLong(newValue))) {
379           return 0L;
380         }
381         // atomic replaced
382         continue;
383       }
384 
385       return oldValue;
386     }
387   }
388 
389   /**
390    * If {@code (key, expectedOldValue)} is currently in the map, this method replaces
391    * {@code expectedOldValue} with {@code newValue} and returns true; otherwise, this method
392    * returns false.
393    *
394    * <p>If {@code expectedOldValue} is zero, this method will succeed if {@code (key, zero)}
395    * is currently in the map, or if {@code key} is not in the map at all.
396    */
397   boolean replace(K key, long expectedOldValue, long newValue) {
398     if (expectedOldValue == 0L) {
399       return putIfAbsent(key, newValue) == 0L;
400     } else {
401       AtomicLong atomic = map.get(key);
402       return (atomic == null) ? false : atomic.compareAndSet(expectedOldValue, newValue);
403     }
404   }
405 
406   /**
407    * If {@code (key, value)} is currently in the map, this method removes it and returns
408    * true; otherwise, this method returns false.
409    */
410   boolean remove(K key, long value) {
411     AtomicLong atomic = map.get(key);
412     if (atomic == null) {
413       return false;
414     }
415 
416     long oldValue = atomic.get();
417     if (oldValue != value) {
418       return false;
419     }
420 
421     if (oldValue == 0L || atomic.compareAndSet(oldValue, 0L)) {
422       // only remove after setting to zero, to avoid concurrent updates
423       map.remove(key, atomic);
424       // succeed even if the remove fails, since the value was already adjusted
425       return true;
426     }
427 
428     // value changed
429     return false;
430   }
431 
432 }